extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::*;
use syn::{parse_macro_input, DeriveInput};

/// rust 过程宏例子
/// https://blog.csdn.net/u013195275/article/details/103875997

#[proc_macro_derive(TraitOp)]
pub fn derive_trait_op(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree
    let ast = parse_macro_input!(input as DeriveInput);

    let name = ast.clone().ident;
    let traitname = Ident::new(&format!("{}Op", name), Span::call_site());
    let extend_macro_name = Ident::new(
        &format!("extend_{}", name.to_string().to_lowercase()),
        Span::call_site(),
    );

    /*
            fn get_attrs(&self) -> HashMap<String, String>;
            fn get_tags(&self) -> Vec<&Self>;
            fn get_text(&self) -> String;
            fn get_name(&self) -> String;
    */
    let attrmethods = match ast.data {
        syn::Data::Struct(ref s) => new_for_struct(&ast, &s.fields),
        _ => panic!("doesn't work with unions yet"),
    };

    let impl_attrmethods = match ast.data {
        syn::Data::Struct(ref s) => impl_new_for_struct(&ast, &s.fields),
        _ => panic!("doesn't work with unions yet"),
    };

    let extend_impl_attrmethods = match ast.data {
        syn::Data::Struct(ref s) => extend_impl_new_for_struct(&ast, &s.fields),
        _ => panic!("doesn't work with unions yet"),
    };

    // Build the output, possibly using quasi-quotation
    let expanded = quote! {
            pub trait #traitname {
                #attrmethods
            }

            impl #traitname for #name {
                #impl_attrmethods
            }

    macro_rules! #extend_macro_name {
      ($e:ty) => {
        impl #traitname for $e {
                #extend_impl_attrmethods
        }
      }
    }

        };

    // println!("{}", expanded);

    // Hand the output tokens back to the compiler
    TokenStream::from(expanded)
}

fn new_for_struct(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2::TokenStream {
    match *fields {
        syn::Fields::Named(ref fields) => {
            new_impl(&ast, &fields.named)
            // new_impl(&ast, Some(&fields.named), true)
        }
        syn::Fields::Unit => {
            panic!("doesn't work with syn::Fields::Unit yet")
            // new_impl(&ast, None, false)
        }
        syn::Fields::Unnamed(ref _fields) => {
            panic!("doesn't work with syn::Fields::Unnamed yet")
            // new_impl(&ast, Some(&fields.unnamed), false)
        }
    }
}

fn new_impl(
    _ast: &syn::DeriveInput,
    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
) -> proc_macro2::TokenStream {
    // let struct_name = &ast.ident;

    let fields: Vec<_> = fields
        .iter()
        .enumerate()
        .map(|(i, f)| FieldExt::new(f, i))
        .collect();
    // let args = fields.iter().map(|f| f.as_arg());
    //let inits = fields.iter().map(|f| f.as_init());
    //let inits = quote![{ #(#inits), *}];

    /*
            fn get_attrs(&self) -> HashMap<String, String>;
            fn get_tags(&self) -> Vec<&Self>;
            fn get_text(&self) -> String;
            fn get_name(&self) -> String;
    */
    // let first_field = fields.iter().next();
    // let single_getter = first_field.unwrap().as_getter();
    // println!("{}", single_getter);
    let getters = fields.iter().map(|f| f.as_getter());

    quote! {
        #(#getters)*
    }
}

fn impl_new_for_struct(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2::TokenStream {
    match *fields {
        syn::Fields::Named(ref fields) => impl_new_impl(&ast, &fields.named),
        syn::Fields::Unit => {
            panic!("doesn't work with syn::Fields::Unit yet")
        }
        syn::Fields::Unnamed(ref _fields) => {
            panic!("doesn't work with syn::Fields::Unnamed yet")
        }
    }
}

fn impl_new_impl(
    _ast: &syn::DeriveInput,
    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
) -> proc_macro2::TokenStream {
    // let struct_name = &ast.ident;

    let fields: Vec<_> = fields
        .iter()
        .enumerate()
        .map(|(i, f)| FieldExt::new(f, i))
        .collect();
    //let args = fields.iter().map(|f| f.as_arg());
    //let inits = fields.iter().map(|f| f.as_init());
    //let inits = quote![{ #(#inits), *}];

    /*
            fn get_attrs(&self) -> HashMap<String, String> {self.attrs};
            fn get_tags(&self) -> Vec<&Self> {self.tags};
            fn get_text(&self) -> String {self.text};
            fn get_name(&self) -> String {self.name};
    */
    // let first_field = fields.iter().next();
    // let single_getter = first_field.unwrap().as_impl_getter();
    // println!("{}", single_getter);
    let getters = fields.iter().map(|f| f.as_impl_getter());

    let ret = quote! {
        #(#getters)*
    };
    // println!("{}", ret);
    ret
}

fn extend_impl_new_for_struct(
    ast: &syn::DeriveInput,
    fields: &syn::Fields,
) -> proc_macro2::TokenStream {
    match *fields {
        syn::Fields::Named(ref fields) => extend_impl_new_impl(&ast, &fields.named),
        syn::Fields::Unit => {
            panic!("doesn't work with syn::Fields::Unit yet")
        }
        syn::Fields::Unnamed(ref _fields) => {
            panic!("doesn't work with syn::Fields::Unnamed yet")
        }
    }
}

fn extend_impl_new_impl(
    ast: &syn::DeriveInput,
    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
) -> proc_macro2::TokenStream {
    let struct_name = &ast.ident;

    let fields: Vec<_> = fields
        .iter()
        .enumerate()
        .map(|(i, f)| FieldExt::new(f, i))
        .collect();
    // let args = fields.iter().map(|f| f.as_arg());
    // let inits = fields.iter().map(|f| f.as_init());
    // let inits = quote![{ #(#inits), *}];

    // let first_field = fields.iter().next();
    // let single_getter = first_field.unwrap().as_extend_impl_getter(struct_name.to_string().to_lowercase());
    // println!("{}", single_getter);
    let getters = fields
        .iter()
        .map(|f| f.as_extend_impl_getter(struct_name.to_string().to_lowercase()));

    let ret = quote! {
        #(#getters)*
    };
    // println!("{}", ret);
    ret
}

struct FieldExt<'a> {
    ty: &'a syn::Type,
    ident: syn::Ident,
    attrs: &'a [syn::Attribute],
    pub _idx: usize,
}

impl<'a> FieldExt<'a> {
    pub fn new(field: &'a syn::Field, _idx: usize) -> FieldExt<'a> {
        FieldExt {
            ty: &field.ty,
            ident: field.ident.clone().unwrap(),
            attrs: &field.attrs,
            _idx,
        }
    }
    // pub fn as_arg(&self) -> proc_macro2::TokenStream {
    //     let f_name = &self.ident;
    //     let ty = &self.ty;
    //     quote!( #f_name: #ty)
    // }
    // pub fn as_init(&self) -> proc_macro2::TokenStream {
    //     let f_name = &self.ident;
    //     let init = quote!(#f_name);
    //     quote!(#f_name: #init)
    // }
    pub fn as_getter(&self) -> proc_macro2::TokenStream {
        let f_name = &self.ident;
        let ty = &self.ty;
        let getter_name = Ident::new(&format!("get_{}", f_name), Span::call_site());
        let ref_name = Ident::new(&format!("ref_{}", f_name), Span::call_site());
        quote!(
            fn #getter_name(&self) -> #ty;
            fn #ref_name(&self) -> &#ty;
        )
    }
    pub fn as_extend_impl_getter(&self, struct_name: String) -> proc_macro2::TokenStream {
        let f_name = &self.ident;
        let ty = &self.ty;
        let getter_name = Ident::new(&format!("get_{}", f_name), Span::call_site());
        let struct_name_ident = Ident::new(&format!("{}", struct_name), Span::call_site());
        let ref_name = Ident::new(&format!("ref_{}", f_name), Span::call_site());
        quote!(
            fn #getter_name(&self) -> #ty {
                self.#struct_name_ident.#f_name.clone()
            }
            fn #ref_name(&self) -> &#ty {
                & self.#struct_name_ident.#f_name
            }
        )
    }
    pub fn as_impl_getter(&self) -> proc_macro2::TokenStream {
        let f_name = &self.ident;
        let ty = &self.ty;
        let getter_name = Ident::new(&format!("get_{}", f_name), Span::call_site());
        let ref_name = Ident::new(&format!("ref_{}", f_name), Span::call_site());
        quote!(
            fn #getter_name(&self) -> #ty {
                self.#f_name.clone()
            }
            fn #ref_name(&self) -> &#ty {
                &self.#f_name
            }

        )
    }
    /// If there is exactly one generic arguments, returns it.
    // fn extract_generic_argument(args: &syn::AngleBracketedGenericArguments) -> Option<syn::Type> {
    //     if args.args.len() == 1 {
    //         match args.args.first().unwrap() {
    //             syn::GenericArgument::Type(ref t) => Some(t.clone()),
    //             _ => None,
    //         }
    //     } else {
    //         None
    //     }
    // }

    pub fn as_expend_macro(&self, struct_name: String) -> proc_macro2::TokenStream {
        let mut tmp_extend_name = None;
        let mut flag_found: bool = false;
        for single_attr in self.attrs {
            // println!("single_attr is {:?}", single_attr);
            if single_attr.path().segments.len() == 1
                && single_attr.path().segments[0].ident == "extendtraitop"
            {
                flag_found = true;

                // println!("{:?}", single_attr.tokens);
                match &single_attr.meta {
                    syn::Meta::Path(_the_path) => {
                        println!("Warning: ignore _the_path");
                    }
                    syn::Meta::List(the_list) => {
                        let mut iter = the_list.tokens.clone().into_iter();
                        let first_token = iter.next();
                        if first_token.is_none() {
                            continue;
                        }
                        match first_token.unwrap() {
                            proc_macro2::TokenTree::Group(group) => {
                                // println!("{:?}", group);

                                let mut name_iter = group.stream().into_iter();
                                let name_token = name_iter.next();
                                if name_token.is_none() {
                                    continue;
                                }
                                let name = name_token.unwrap();
                                // println!("{:?}", name);

                                match name {
                                    proc_macro2::TokenTree::Ident(ident) => {
                                        tmp_extend_name = Some(ident);
                                    }
                                    _ => {
                                        continue;
                                    }
                                }
                            }
                            _ => {
                                continue;
                            }
                        }
                    }
                    syn::Meta::NameValue(_the_name_value) => {
                        println!("Warning: ignore _the_name_value");
                    }
                }
            }
        }

        if flag_found == false {
            return quote!();
        }
        let f_name = &self.ident;
        let ty = &self.ty;
        assert!(
            quote!(#f_name).to_string() == quote!(#ty).to_string().to_lowercase(),
            "expecting {}:{} in {} found {}:{}",
            quote!(#ty).to_string().to_lowercase(),
            quote!(#ty).to_string(),
            struct_name,
            quote!(#f_name).to_string(),
            quote!(#ty).to_string()
        );
        let extend_name = Ident::new(
            &format!("extend_{}", quote!(#ty).to_string().to_lowercase()),
            Span::call_site(),
        );
        let struct_name_ident = Ident::new(&format!("{}", struct_name), Span::call_site());
        if tmp_extend_name.is_none() {
            quote!( #extend_name!(#struct_name_ident);)
        } else {
            let extend_trait_name = tmp_extend_name.unwrap();
            quote!(
                #extend_name!(#struct_name_ident);

                impl #extend_trait_name for #struct_name_ident{}
            )
        }
    }
}

#[proc_macro_derive(ExtendTraitOp, attributes(extendtraitop))]
pub fn derive_extend_trait_op(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree
    let ast = parse_macro_input!(input as DeriveInput);

    // let name = ast.clone().ident;
    // let traitname = Ident::new(&format!("{}Op", name), Span::call_site());

    let expend_attrs = match ast.data {
        syn::Data::Struct(ref s) => expend_for_struct(&ast, &s.fields),
        _ => panic!("doesn't work with unions yet"),
    };

    // Build the output, possibly using quasi-quotation
    let expanded = quote! {
        #expend_attrs
    };

    // println!("{}", expanded);

    // Hand the output tokens back to the compiler
    TokenStream::from(expanded)
}

fn expend_for_struct(ast: &syn::DeriveInput, fields: &syn::Fields) -> proc_macro2::TokenStream {
    match *fields {
        syn::Fields::Named(ref fields) => expend_impl(&ast, &fields.named),
        syn::Fields::Unit => {
            panic!("doesn't work with syn::Fields::Unit yet")
        }
        syn::Fields::Unnamed(ref _fields) => {
            panic!("doesn't work with syn::Fields::Unnamed yet")
        }
    }
}

fn expend_impl(
    ast: &syn::DeriveInput,
    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
) -> proc_macro2::TokenStream {
    let struct_name = &ast.ident;

    let fields: Vec<_> = fields
        .iter()
        .enumerate()
        .map(|(i, f)| FieldExt::new(f, i))
        .collect();
    // let args = fields.iter().map(|f| f.as_arg());
    // let inits = fields.iter().map(|f| f.as_init());
    // let inits = quote![{ #(#inits), *}];

    // let first_field = fields.iter().next();
    // let single_getter = first_field.unwrap().as_expend_macro(struct_name.to_string());
    // println!("{}", single_getter);
    let getters = fields
        .iter()
        .map(|f| f.as_expend_macro(struct_name.to_string()));

    quote! {
        #(#getters)*
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}
